package han.cirno.corrupt.activity.main

import android.app.Activity
import android.graphics.BitmapFactory
import android.util.Base64
import androidx.activity.result.ActivityResult
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.palette.graphics.Palette
import dagger.hilt.android.lifecycle.HiltViewModel
import han.cirno.corrupt.Application
import han.cirno.corrupt.R
import han.cirno.corrupt.components.cclib.CCLib
import han.cirno.corrupt.components.crash.CrashUtils
import han.cirno.corrupt.components.monet.MonetGenerator
import han.cirno.corrupt.components.refresh.BackgroundTask
import han.cirno.corrupt.components.refresh.RefreshQueue
import han.cirno.corrupt.components.refresh.StatusReceiver
import han.cirno.corrupt.components.refresh.impl.CheckAndFetchTask
import han.cirno.corrupt.components.refresh.impl.GateTask
import han.cirno.corrupt.components.refresh.impl.SimplePassTask
import han.cirno.corrupt.components.refresh.impl.UpdateCheckTask
import han.cirno.corrupt.components.school.SchoolImpl
import han.cirno.corrupt.components.school.SchoolProvider
import han.cirno.corrupt.compose.preferenceFlow
import han.cirno.corrupt.data.DB
import han.cirno.corrupt.data.DataKeys
import han.cirno.corrupt.data.SettingKeys
import han.cirno.corrupt.data.UserUnstructuredData
import han.cirno.corrupt.data.instances.UserSchoolData
import han.cirno.corrupt.getPreferenceKey
import han.cirno.corrupt.util.SerialUtil
import han.cirno.corrupt.util.ViewUtil.getString
import han.cirno.corrupt.util.chain
import han.cirno.corrupt.writeDataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import javax.inject.Inject

@HiltViewModel
class MainActivityViewModel @Inject constructor():ViewModel(),StatusReceiver{
    private val wipeDataCountdownState = mutableIntStateOf(5)
    val isLoggingState= mutableStateOf(false)
    val loginLoadingTextState= mutableStateOf("")
    val functionsRefreshTrigger= mutableIntStateOf(0)

    val allExamsFlow= DB.getInstance().examDao().getAllListFlow()
        .map {
            it.sortedWith(compareBy(
                {i->i.date.first.toIntOrNull()?:Int.MAX_VALUE},
                {i->i.date.second.toIntOrNull()?:Int.MAX_VALUE},
                {i->i.date.third.toIntOrNull()?:Int.MAX_VALUE},
                {i->i.fromTime.first.toIntOrNull()?:Int.MAX_VALUE},
                {i->i.fromTime.second.toIntOrNull()?:Int.MAX_VALUE},
                {i->i.name}
            ))
        }
        .distinctUntilChangedBy { it.isEmpty() }
        .stateIn(
            viewModelScope,
            SharingStarted.Lazily,
            emptyList())
    val allScoresFlow= DB.getInstance().scoreDao().getAllListFlow()
        .map {
            it.sortedWith(compareBy { i -> i.name })
        }
        .distinctUntilChangedBy { it.isEmpty() }
        .stateIn(
            viewModelScope,
            SharingStarted.Lazily,
            emptyList())
    val reportsFlow= flow {
        val tempFilePath=Application.application.cacheDir
        val listFiles = tempFilePath.listFiles()
        if (listFiles==null){
            emit(mutableListOf())
            return@flow
        }
        emit(listFiles.mapNotNull {
            val name=it.name
            if(!name.endsWith(".txt"))return@mapNotNull null
            val strings = name.substring(0,name.length-4).split("-")
            if(strings.size!=2)return@mapNotNull null
            return@mapNotNull Triple(strings[0],strings[1],it.absolutePath)
        }.sortedByDescending { it.second.substring(0,System.currentTimeMillis().toString().length)
            .toLongOrNull()?:0L })
    }.flowOn(Dispatchers.IO)
    //TODO auto set default value
    private val configUpdateCheckState= preferenceFlow(SettingKeys.ENABLE_UPDATE_CHECK,true)
    val configImageState= preferenceFlow(SettingKeys.RAW_BACKGROUND,null as String?)
    val configNightOverrideState= preferenceFlow(SettingKeys.SELECT_OVERRIDE_NIGHT,"")
    val configEnableBackgroundState = preferenceFlow(SettingKeys.ENABLE_BACKGROUND,false)
    val configBackgroundAlphaState= preferenceFlow(SettingKeys.SLIDER_BACKGROUND_ALPHA, 0.1f)
    val configTimeLineInspectorState= preferenceFlow(SettingKeys.ENABLE_SHOW_TIME_INSPECTOR,true)
    val configTimeLineInspectorAlphaState= preferenceFlow(SettingKeys.SLIDER_TIME_INSPECTOR_ALPHA,1f)
    val configDayInspectorAlphaState= preferenceFlow(SettingKeys.SLIDER_DAY_INSPECTOR_ALPHA,0.1f)
    val configDayInspectorState=preferenceFlow(SettingKeys.ENABLE_SHOW_DAY_INSPECTOR,true)
    val configAppBarAlphaState= preferenceFlow(SettingKeys.SLIDER_APPBAR_ALPHA,1f)
    val configNavBarAlphaState= preferenceFlow(SettingKeys.SLIDER_NAVBAR_ALPHA,1f)
    val configColorThemeState= preferenceFlow(SettingKeys.SELECT_COLOR_SCHEME,"")
    val configPictureColorThemeState= preferenceFlow(SettingKeys.RAW_PICTURE_COLOR_SCHEME,"")
    val configUserColorThemeState=preferenceFlow(SettingKeys.RAW_USER_COLOR_SCHEME,"")
    val configRefreshFrequency= preferenceFlow(SettingKeys.SELECT_REFRESH_FREQUENCY,"")
    val configLastRefreshTime= preferenceFlow(SettingKeys.RAW_USER_LAST_REFRESH_TIME,0L)
    val configLanguage= preferenceFlow(SettingKeys.SELECT_LANGUAGE,"0")
    val configLegacyColor= preferenceFlow(SettingKeys.ENABLE_LEGACY_COLOR,false)

    val isLoggedState= preferenceFlow(DataKeys.DATA_LOGGED,false)

    val updateVersionState= mutableStateOf("NaN")
    val updateSizeState= mutableStateOf("NaN")
    val updateAvailableState= mutableStateOf(false)
    val updateStatusState= mutableIntStateOf(0)
    val updateProgressState= mutableStateOf("")

    val loadingState= mutableStateOf("" to BackgroundTask.Status.None)

    private val backgroundTasks:Array<suspend ()->BackgroundTask<MainActivityViewModel>> = arrayOf(
        {UpdateCheckTask(configUpdateCheckState.first())},
        {GateTask(isLoggedState.first())},
        {CheckAndFetchTask(SchoolProvider.INFO_CLASSES, R.string.navigator_classes)},
        {CheckAndFetchTask(SchoolProvider.INFO_EXAMS, R.string.functions_exams)},
        {CheckAndFetchTask(SchoolProvider.INFO_SCORE, R.string.functions_scores)},
        {SimplePassTask{functionsRefreshTrigger.intValue++}},
        {SimplePassTask{postAllDone()}}
    )
    private val refreshQueue:RefreshQueue<MainActivityViewModel> =RefreshQueue(viewModelScope,this)

    fun refresh()= refreshQueue.enqueue(*backgroundTasks)

    fun performUpdate(){
        updateStatusState.intValue=1
        viewModelScope.launch {
            CCLib.instance.networkFetchFile("latest.apk",Application.application.cacheDir!!.absolutePath){now,total->
                updateProgressState.value="$now;$total"
            }.onFailure {
                updateStatusState.intValue=0
            }.onSuccess {
                updateStatusState.intValue=2
            }
        }
    }

    fun wipeData(reset:Boolean):Boolean{
        if(reset){
            wipeDataCountdownState.intValue=5
            return false
        }
        wipeDataCountdownState.intValue--
        if (wipeDataCountdownState.intValue<=1){
            UserUnstructuredData.clearData()
            return true
        }
        return false
    }

    fun handleOpenedFile(result:ActivityResult){
        if (result.resultCode!=Activity.RESULT_OK)return
        val data=(result.data?:return).data?:return
        val bytes = Application.application.contentResolver.openInputStream(data)?.readBytes() ?: return
        BitmapFactory.decodeByteArray(bytes,0,bytes.size)?:return
        val b64 = Base64.encodeToString(bytes, Base64.DEFAULT)
        runBlocking { writeDataStore(getPreferenceKey<String>(SettingKeys.RAW_BACKGROUND),b64)}
        updateColorThemeFromBackground()
    }

    fun submitLogin(id:String, password:String,school: UserSchoolData.School,finishedCallback:()->Unit){
        if (isLoggingState.value)return
        isLoggingState.value=true
        if(school==UserSchoolData.School.None)return
        runBlocking { writeDataStore(getPreferenceKey(DataKeys.DATA_SCHOOL),school.name) }
        val provider = SchoolImpl.getImpl(school)!!
        viewModelScope.launch {
            withContext(Dispatchers.IO){
                return@withContext provider.performLogin(id,password,object : SchoolProvider.LoginCallback{
                    override fun onProgress(infoStatement: String) { loginLoadingTextState.value=infoStatement }
                    override fun onPartialFailed(
                        infoStatement: String,
                        fatal: Boolean,
                        exception: Exception
                    ) { loginLoadingTextState.value=infoStatement }
                })
            }.onSuccess {
                runBlocking { writeDataStore(getPreferenceKey(DataKeys.DATA_LOGGED),true) }
                finishedCallback()
            }.onFailure {
                runBlocking { writeDataStore(getPreferenceKey(DataKeys.DATA_LOGGED),false) }
            }.run {
                isLoggingState.value=false
            }
        }
    }

    fun updateColorThemeFromBackground()=runBlocking backgroundSetup@ {
        val imageBase64=configImageState.first()?:return@backgroundSetup
        val imageByteArray=Base64.decode(imageBase64,Base64.DEFAULT)
        val imageBitmap=BitmapFactory.decodeByteArray(imageByteArray,0,imageByteArray.size)
        val palette= Palette.from(imageBitmap).generate()
        val mainColorLight=(
                chain(
                    palette.vibrantSwatch,
                    palette.mutedSwatch,
                    palette.lightVibrantSwatch,
                    palette.darkVibrantSwatch,
                    palette.lightMutedSwatch,
                    palette.darkMutedSwatch,
                    palette.dominantSwatch,
                )?:return@backgroundSetup).rgb
        val monetPalette=MonetGenerator.generateColors(
            mainColorLight,mainColorLight)
        writeDataStore(
            getPreferenceKey(SettingKeys.RAW_PICTURE_COLOR_SCHEME),
            Base64.encodeToString(SerialUtil.serialize(monetPalette),Base64.DEFAULT))
    }

    override fun postStartStatus(name: Int?) {
        loadingState.value = (getString(name?:return) to BackgroundTask.Status.Loading)
    }

    override fun postSuccessStatus(name: Int?) {
        loadingState.value = (getString(name?:return)  to BackgroundTask.Status.Finished)
    }

    override fun postFailedStatus(name: Int?, throwable: Throwable) {
        CrashUtils.makeTaskFailedReport(name, throwable)
        //TODO Add throwable handle
        loadingState.value = (getString(name?:return)+"||"+throwable.stackTraceToString()
                to BackgroundTask.Status.Failed)
    }

    override fun postAllDone() {
        loadingState.value = (getString(R.string.main_refresh_finished) to BackgroundTask.Status.AllDone)
    }
}